package com.gateway.springboot.starter.plugin.security;

import com.gateway.plugin.api.GatewayPlugin;
import com.gateway.plugin.base.handler.PluginDataHandler;
import com.gateway.plugin.security.uaa.extract.BasicExtractor;
import com.gateway.plugin.security.uaa.extract.MobileApplicationExtractor;
import com.gateway.security.SecurityGatewayFilter;
import com.gateway.security.authentication.convert.ClientTokenAuthenticationConverter;
import com.gateway.security.authentication.convert.ConfigServerSecurityTokenRepository;
import com.gateway.security.authentication.manager.SecurityReactiveAuthenticationManager;
import com.gateway.security.authorization.GatewayReactiveAuthorizationManager;
import com.gateway.security.common.extract.ClientExtractor;
import com.gateway.security.common.matcher.AuthenticationServerWebExchangeMatcher;
import com.gateway.security.common.matcher.AuthorizationServerWebExchangeMatcher;
import com.gateway.security.common.matcher.PermitAuthorizationServerWebExchangeMatcher;
import com.gateway.security.common.token.ServerSecurityTokenRepository;
import com.gateway.security.endpoint.TokenEndpointService;
import com.gateway.security.endpoint.UaaTokenEndpointService;
import com.gateway.security.handler.SecurityPluginDataHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancedExchangeFilterFunction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @program: api-gateway
 * @description:
 * @author: YuKai Fan
 * @create: 2025/2/12 16:37
 **/
@Configuration(enforceUniqueMethods = false)
@EnableWebFluxSecurity
public class SecurityPluginConfiguration {

    private static final List<ServerWebExchangeMatcher> MATCHERS = new CopyOnWriteArrayList<>();

    static {
        MATCHERS.add(AuthorizationServerWebExchangeMatcher.getInstance());
    }

    private static final OrServerWebExchangeMatcher OR_MATCHER = new OrServerWebExchangeMatcher(MATCHERS);

    /**
     * security plugin data handler
     * @return
     */
    @Bean
    public PluginDataHandler securityPluginDataHandler(final ObjectProvider<TokenEndpointService> objectProvider) {
        return new SecurityPluginDataHandler(Objects.requireNonNull(objectProvider.getIfAvailable()));
    }

    @Bean
    public ClientExtractor basicExtractor() {
        return new BasicExtractor();
    }

    @Bean
    public ClientExtractor mobileApplicationExtractor() {
        return new MobileApplicationExtractor();
    }

    @Bean
    @ConditionalOnClass(LoadBalancedExchangeFilterFunction.class)
    public TokenEndpointService tokenEndpointService(LoadBalancedExchangeFilterFunction function) {
        return new UaaTokenEndpointService(function);
    }
//
//    @Bean
//    @ConditionalOnBean(value = {HttpClientProperties.class, HttpClient.class})
//    @ConditionalOnMissingBean(LoadBalancedExchangeFilterFunction.class)
//    public TokenEndpointService tokenEndpointService(
//            final ObjectProvider<HttpClientProperties> properties,
//            final ObjectProvider<HttpClient> httpClient
//    ) {
//        WebClient webClient = WebClient.builder()
//                .exchangeStrategies(ExchangeStrategies.builder()
//                        .codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(properties.getIfAvailable().getMaxInMemorySize() * 1024 * 1024))
//                        .build())
//                .clientConnector(new ReactorClientHttpConnector(Objects.requireNonNull(httpClient.getIfAvailable())))
//                .build();
//        return new UaaTokenEndpointService(webClient);
//    }

    @Bean
    public ServerSecurityTokenRepository configServerSecurityTokenRepository() {
        return new ConfigServerSecurityTokenRepository();
    }

    /**
     * Build SecurityWebFilterChain.
     *
     * @param http  The ServerHttpSecurity Instance
     * @param objectProvider 认证端点
     * @return The SecurityWebFilterChain
     */
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(final ServerHttpSecurity http,
                                                         final ObjectProvider<TokenEndpointService> objectProvider,
                                                         final ObjectProvider<ServerSecurityTokenRepository> repositoryObjectProvider) {
        AuthenticationWebFilter filter = new AuthenticationWebFilter(new SecurityReactiveAuthenticationManager(Objects.requireNonNull(objectProvider.getIfAvailable())));
        filter.setRequiresAuthenticationMatcher(AuthenticationServerWebExchangeMatcher.getInstance());
        filter.setServerAuthenticationConverter(new ClientTokenAuthenticationConverter(Objects.requireNonNull(repositoryObjectProvider.getIfAvailable())));
        return http
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                // 认证过滤器
                .addFilterAt(filter, SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAfter(new SecurityGatewayFilter(), SecurityWebFiltersOrder.AUTHORIZATION)
                .authorizeExchange(exchangeSpec ->
                    exchangeSpec
                            .matchers(PathRequest.toStaticResources().atCommonLocations(), PermitAuthorizationServerWebExchangeMatcher.getInstance())
                            .permitAll()
                            .pathMatchers(HttpMethod.OPTIONS)
                            .permitAll()
                            .matchers(OR_MATCHER)
                            .access(new GatewayReactiveAuthorizationManager())
                            .anyExchange().permitAll()
                )
                .build();

    }
}
